﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.IO;
using Microsoft.Win32;
using SharpPcap;
using SharpPcap.Packets;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows.Threading;

namespace WC3Game
{
    public static class Session
    {
        public delegate void GameAddedEventHandler();
        public static GameAddedEventHandler GameAdded;

        public delegate void GameCreatedEventHandler(String gameName);
        public static GameCreatedEventHandler GameCreated;

        public delegate void PlayerEventHandler(String playerName);
        public static PlayerEventHandler PlayerAdded;
        public static PlayerEventHandler PlayerRemoved;
        public static PlayerEventHandler PlayerQuit;

        static MyObservableCollection<Game> gameList = new MyObservableCollection<Game>();
        static List<Player> temporaryPlayers = new List<Player>();

        static IncompleteData incompleteData = null;

        public static List<Player> TemporaryPlayers
        {
            get { return Session.temporaryPlayers; }
            set { Session.temporaryPlayers = value; }
        }

        static Game currentGame = null;

        public static Game CurrentGame
        {
            get { return currentGame; }
        }

        public static Game CreateGame(byte[] commandData)
        {
            currentGame = Game.CreateGame(commandData);
            return currentGame;
        }

        public static void AddCurrentGame()
        {
            gameList.Add(currentGame);
            currentGame = null;

            if (GameAdded != null)
                GameAdded();
        }

        public static MyObservableCollection<Game> GameList
        {
            get { return gameList; }
            set { gameList = value; }
        }

        #region Packet Constants
        public enum GamePacketConstants : byte
        {
            BNET_PROTOCOL = 0xff,
            W3GS_PROTOCOL = 0xf7
        }

        public enum BattleNetPacketIDConstants : byte
        {
            // Battle.net Messages Constants
            // -------------------

            SID_NULL = 0x00,
            SID_STOPADV = 0x02,
            SID_SERVERLIST = 0x04,
            SID_CLIENTID = 0x05,
            SID_STARTVERSIONING = 0x06,
            SID_REPORTVERSION = 0x07,
            SID_STARTADVEX = 0x08,
            //SID_GETADVLISTEX = 0x09,
            SID_ENTERCHAT = 0x0A,
            SID_GETCHANNELLIST = 0x0B,
            SID_JOINCHANNEL = 0x0C,
            SID_CHATCOMMAND = 0x0E,
            SID_CHATEVENT = 0x0F,
            SID_LEAVECHAT = 0x10,
            SID_LOCALEINFO = 0x12,
            SID_FLOODDETECTED = 0x13,
            SID_UDPPINGRESPONSE = 0x14,
            SID_CHECKAD = 0x15,
            SID_CLICKAD = 0x16,
            SID_REGISTRY = 0x18,
            SID_MESSAGEBOX = 0x19,
            SID_STARTADVEX2 = 0x1A,
            SID_GAMEDATAADDRESS = 0x1B,
            SID_STARTADVEX3 = 0x1C,
            SID_LOGONCHALLENGEEX = 0x1D,
            SID_CLIENTID2 = 0x1E,
            SID_LEAVEGAME = 0x1F,
            SID_DISPLAYAD = 0x21,
            SID_NOTIFYJOIN = 0x22,
            SID_PING = 0x25,
            SID_READUSERDATA = 0x26,
            SID_WRITEUSERDATA = 0x27,
            SID_LOGONCHALLENGE = 0x28,
            SID_LOGONRESPONSE = 0x29,
            SID_CREATEACCOUNT = 0x2A,
            SID_SYSTEMINFO = 0x2B,
            SID_GAMERESULT = 0x2C,
            SID_GETICONDATA = 0x2D,
            SID_GETLADDERDATA = 0x2E,
            SID_FINDLADDERUSER = 0x2F,
            SID_CDKEY = 0x30,
            SID_CHANGEPASSWORD = 0x31,
            SID_CHECKDATAFILE = 0x32,
            SID_GETFILETIME = 0x33,
            SID_QUERYREALMS = 0x34,
            SID_PROFILE = 0x35,
            SID_CDKEY2 = 0x36,
            SID_LOGONRESPONSE2 = 0x3A,
            SID_CHECKDATAFILE2 = 0x3C,
            SID_CREATEACCOUNT2 = 0x3D,
            SID_LOGONREALMEX = 0x3E,
            SID_STARTVERSIONING2 = 0x3F,
            SID_QUERYREALMS2 = 0x40,
            SID_QUERYADURL = 0x41,
            //SID_WARCRAFTGENERAL = 0x44,
            SID_NETGAMEPORT = 0x45,
            SID_NEWS_INFO = 0x46,
            SID_OPTIONALWORK = 0x4A,
            SID_EXTRAWORK = 0x4B,
            SID_REQUIREDWORK = 0x4C,
            SID_TOURNAMENT = 0x4E,
            SID_AUTH_INFO = 0x50,
            SID_AUTH_CHECK = 0x51,
            SID_AUTH_ACCOUNTCREATE = 0x52,
            SID_AUTH_ACCOUNTLOGON = 0x53,
            SID_AUTH_ACCOUNTLOGONPROOF = 0x54,
            SID_AUTH_ACCOUNTCHANGE = 0x55,
            SID_AUTH_ACCOUNTCHANGEPROOF = 0x56,
            SID_AUTH_ACCOUNTUPGRADE = 0x57,
            SID_AUTH_ACCOUNTUPGRADEPROOF = 0x58,
            SID_SETEMAIL = 0x59,
            SID_RESETPASSWORD = 0x5A,
            SID_CHANGEEMAIL = 0x5B,
            SID_SWITCHPRODUCT = 0x5C,
            //SID_WARDEN = 0x5E,
            SID_GAMEPLAYERSEARCH = 0x60,
            SID_FRIENDSLIST = 0x65,
            SID_FRIENDSUPDATE = 0x66,
            SID_FRIENDSADD = 0x67,
            SID_FRIENDSREMOVE = 0x68,
            SID_FRIENDSPOSITION = 0x69,
            SID_CLANFINDCANDIDATES = 0x70,
            SID_CLANINVITEMULTIPLE = 0x71,
            SID_CLANCREATIONINVITATION = 0x72,
            SID_CLANDISBAND = 0x73,
            SID_CLANMAKECHIEFTAIN = 0x74,
            SID_CLANINFO = 0x75,
            SID_CLANQUITNOTIFY = 0x76,
            SID_CLANINVITATION = 0x77,
            SID_CLANREMOVEMEMBER = 0x78,
            SID_CLANINVITATIONRESPONSE = 0x79,
            SID_CLANRANKCHANGE = 0x7A,
            SID_CLANSETMOTD = 0x7B,
            SID_CLANMOTD = 0x7C,
            SID_CLANMEMBERLIST = 0x7D,
            SID_CLANMEMBERREMOVED = 0x7E,
            SID_CLANMEMBERSTATUSCHANGE = 0x7F,
            SID_CLANMEMBERRANKCHANGE = 0x81,
            SID_CLANMEMBERINFORMATION = 0x82,

            // Battle.net UDP Messages Constants
            // -------------------

            //PKT_CLIENTREQ = 0x03,
            //PKT_SERVERPING = 0x05,
            //PKT_KEEPALIVE = 0x07,
            //PKT_CONNTEST = 0x08,
            //PKT_CONNTEST2 = 0x09
        };

        public enum W3GSPacketIDConstants : byte
        {
            // W3GS Message Constants
            // -------------------

            W3GS_PING_FROM_HOST = 0x01,
            W3GS_SLOTINFOJOIN = 0x04,
            W3GS_REJECTJOIN = 0x05,
            W3GS_PLAYERINFO = 0x06,
            W3GS_PLAYERLEAVE_OTHERS = 0x07,
            W3GS_GAMELOADED_OTHERS = 0x08,
            W3GS_SLOTINFO = 0x09,
            W3GS_COUNTDOWN_START = 0x0A,
            W3GS_COUNTDOWN_END = 0x0B,
            W3GS_INCOMING_ACTION = 0x0C,
            W3GS_CHAT_FROM_HOST = 0x0F,
            W3GS_START_LAG = 0x10,
            W3GS_STOP_LAG = 0x11,
            W3GS_HOST_KICK_PLAYER = 0x1C,
            W3GS_REQJOIN = 0x1E,
            W3GS_LEAVEGAME = 0x21,
            W3GS_GAMELOADED_SELF = 0x23,
            W3GS_OUTGOING_ACTION = 0x26,
            W3GS_OUTGOING_KEEPALIVE = 0x27,
            W3GS_CHAT_TO_HOST = 0x28,
            W3GS_DROPREQ = 0x29,
            W3GS_SEARCHGAME = 0x2F, //(UDP/LAN)
            W3GS_GAMEINFO = 0x30, //(UDP/LAN)
            W3GS_CREATEGAME = 0x31, //(UDP/LAN)
            W3GS_REFRESHGAME = 0x32, //(UDP/LAN)
            W3GS_DECREATEGAME = 0x33, //(UDP/LAN)
            W3GS_CHAT_OTHERS = 0x34,
            W3GS_PING_FROM_OTHERS = 0x35,
            W3GS_PONG_TO_OTHERS = 0x36,
            W3GS_MAPCHECK = 0x3D,
            W3GS_STARTDOWNLOAD = 0x3F,
            W3GS_MAPSIZE = 0x42,
            W3GS_MAPPART = 0x43,
            W3GS_MAPPARTOK = 0x44,
            W3GS_MAPPARTNOTOK = 0x45, // just a guess, received this packet after forgetting to send a crc in W3GS_MAPPART (f7 45 0a 00 01 02 01 00 00 00)
            W3GS_PONG_TO_HOST = 0x46,
            W3GS_INCOMING_ACTION2 = 0x48 // received this packet when there are too many actions to fit in W3GS_INCOMING_ACTION
        };
        #endregion

        static int packetErrors = 0;
        static int frameCounter = 0;

        public static int FrameCounter
        {
            get { return Session.frameCounter; }
        }

        public static void ProcessPacket(PcapCaptureEventArgs e)
        {
            frameCounter++;

            byte[] packetPayload = null;

            int exceptionCount = 0;

            TCPPacket tcpPacket = null;
            UDPPacket udpPacket = null;

            PacketInformation packet = null;

#if DEBUG
            bool appendingPacket = false;
#endif

            if (e.Packet is TCPPacket)
            {
                tcpPacket = (TCPPacket)e.Packet;

                if (!(tcpPacket.SourcePort >= 6112 && tcpPacket.SourcePort <= 6119) &&
                    !(tcpPacket.DestinationPort >= 6112 && tcpPacket.DestinationPort <= 6119))
                    return;

                if (incompleteData != null &&
                    tcpPacket.SourceAddress.ToString() == incompleteData.SrcAddr &&
                    tcpPacket.DestinationAddress.ToString() == incompleteData.DstAddr)
                {
#if DEBUG
                    Console.WriteLine("Appending Packets");
                    appendingPacket = true;
#endif

                    List<byte> reassmbledData = new List<byte>();
                    reassmbledData.AddRange(incompleteData.Data);
                    reassmbledData.AddRange(tcpPacket.Data);

                    packetPayload = reassmbledData.ToArray();

                    incompleteData = null;
                }
                else
                {
                    packetPayload = tcpPacket.Data;
                }
                packet = new PacketInformation(PacketInformation.PacketProtocol.TCP, packetPayload);
#if DEBUG
                packet.PortInfo = String.Format("{0}/{1}",
                    tcpPacket.SourcePort,
                    tcpPacket.DestinationPort
                    );
#endif
            }
            else if (e.Packet is UDPPacket)
            {
                udpPacket = (UDPPacket)e.Packet;

                if (incompleteData != null &&
                    udpPacket.SourceAddress.ToString() == incompleteData.SrcAddr &&
                    udpPacket.DestinationAddress.ToString() == incompleteData.DstAddr)
                {
#if DEBUG
                    Console.WriteLine("Appending Packets");
                    appendingPacket = true;
#endif

                    List<byte> reassmbledData = new List<byte>();
                    reassmbledData.AddRange(incompleteData.Data);
                    reassmbledData.AddRange(udpPacket.Data);

                    packetPayload = reassmbledData.ToArray();

                    incompleteData = null;
                }
                else
                {
                    packetPayload = udpPacket.Data;
                }

                packet = new PacketInformation(PacketInformation.PacketProtocol.UDP, packetPayload);
#if DEBUG
                packet.PortInfo = String.Format("{0}/{1}",
                    udpPacket.SourcePort,
                    udpPacket.DestinationPort
                    );
#endif
            }
            else
            {
                // Discard packet.
                return;
            }

            if (packetPayload.Length < 4)
            {
                // Nothing useful in packet.
                // Discard it.

                return;
            }

            bool potentialPacketFound = false;

            foreach (byte headerVal in Enum.GetValues(typeof(GamePacketConstants)))
            {
                if (packetPayload[0] == (byte)GamePacketConstants.BNET_PROTOCOL &&
                    Enum.IsDefined(typeof(BattleNetPacketIDConstants), packetPayload[1]))
                {
                    potentialPacketFound = true;
                    break;

                }

                if (packetPayload[0] == (byte)GamePacketConstants.W3GS_PROTOCOL &&
                    Enum.IsDefined(typeof(W3GSPacketIDConstants), packetPayload[1]))
                {

                    potentialPacketFound = true;
                    break;
                }

            }

            if (potentialPacketFound)
            {
                // Found potential packet -- process.

                int commandCount = packet.Process(out exceptionCount, e.Packet.Timeval);

#if DEBUG
                if (appendingPacket)
                {
                    if (commandCount > 0)
                        Console.WriteLine("Successfully processed appended packet");
                    else
                        Console.WriteLine("Appended packet not processed");

                    appendingPacket = false;
                }
#endif

                if (commandCount == -1)
                {
                    // Packet appeared incomplete.
                    // Hold onto it, and try to assemble a full payload.

                    IncompleteData i = new IncompleteData();
                    i.Data = packetPayload;

                    if (tcpPacket != null)
                    {
                        i.SrcAddr = tcpPacket.SourceAddress.ToString();
                        i.DstAddr = tcpPacket.DestinationAddress.ToString();
                    }

                    if (udpPacket != null)
                    {
                        i.SrcAddr = udpPacket.SourceAddress.ToString();
                        i.DstAddr = udpPacket.DestinationAddress.ToString();
                    }

                    incompleteData = i;
                }

#if DEBUG
                if (exceptionCount > 0)
                {
                    packetErrors += exceptionCount;
                    Console.WriteLine("Found Exceptions : {0}",
                        packetErrors
                        );


                }
#endif
            }
            else
            {
                if (tcpPacket != null)
                {
                    if (tcpPacket.Data[0] == 0xf7)
                    {
                        // Interesting packet.
                        int x = 0;
                    }
                }
            }

        }
    }

    public class MyObservableCollection<T> : ObservableCollection<T>
    {
        public override event NotifyCollectionChangedEventHandler CollectionChanged;

        protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {

            // Be nice - use BlockReentrancy like MSDN said

            using (BlockReentrancy())
            {

                System.Collections.Specialized.NotifyCollectionChangedEventHandler eventHandler = CollectionChanged;

                if (eventHandler == null)

                    return;



                Delegate[] delegates = eventHandler.GetInvocationList();

                // Walk thru invocation list

                foreach (System.Collections.Specialized.NotifyCollectionChangedEventHandler handler in delegates)
                {

                    DispatcherObject dispatcherObject = handler.Target as DispatcherObject;

                    // If the subscriber is a DispatcherObject and different thread

                    if (dispatcherObject != null && dispatcherObject.CheckAccess() == false)
                    {

                        // Invoke handler in the target dispatcher's thread

                        dispatcherObject.Dispatcher.Invoke(DispatcherPriority.DataBind, handler, this, e);

                    }

                    else // Execute handler as is

                        handler(this, e);

                }

            }

        }
    }

    public class IncompleteData
    {
        public byte[] Data;
        public String SrcAddr;
        public String DstAddr;
    }
}
